﻿using System;
using System.Collections.Specialized;
using System.Configuration;
using System.IO;
using System.Linq.Expressions;
using System.Reflection;
using System.Xml;
using System.Xml.Serialization;

namespace ImporterForm
{
    /// <summary>
    /// Implements code that allows the plug-in to ready configuration settings
    /// </summary>
    /// <remarks>The <see cref="ConfigReader"/> class allows the plug-in to read settings
    /// from its own app.config file instead of the default configuration file of the host application.</remarks>
    public class ConfigReader
    {
        #region Fields (8)

        /// <summary>
        /// Delimits values in a collection
        /// </summary>
        /// <value>"\r\n"</value>
        private const string collectionSeparater = @"\r\n";

        /// <summary>
        /// Stores the assembly for this instance of the <see cref="ConfigReader"/> class
        /// </summary>
        private Assembly configAssembly;

        /// <summary>
        /// File extension for application configuration files
        /// </summary>
        /// <value>".config"</value>
        private const string configFileExtension = ".config";

        /// <summary>
        /// Stores the configuration settings for this instance of the <see cref="ConfigReader"/> class
        /// </summary>
        private Configuration configSettings;

        /// <summary>
        /// Default section group name
        /// </summary>
        /// <value>"applicationSettings"</value>
        private const string defaultSectionGroupName = "applicationSettings";

        /// <summary>
        /// Default section name
        /// </summary>
        /// <value>".Properties.Settings"</value>
        private const string defaultSectionNameSuffix = ".Properties.Settings";

        /// <summary>
        /// Stores the section group name for this instance of the <see cref="ConfigReader"/> class
        /// </summary>
        private string settingSectionGroupName;

        /// <summary>
        /// Stores the section name for this instance of the <see cref="ConfigReader"/> class
        /// </summary>
        private string settingSectionName;

        #endregion Fields

        #region Constructors (2)

        /// <summary>
        /// Initializes a new instance of the <see cref="ConfigReader"/> class
        /// </summary>
        /// <param name="namedAssembly">The assembly for which to read settings</param>
        public ConfigReader(Assembly namedAssembly)
        {
            configAssembly = namedAssembly;
            settingSectionGroupName = defaultSectionGroupName;
            settingSectionName = String.Concat(namedAssembly.GetName().Name, defaultSectionNameSuffix);
            GetConfig(namedAssembly);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ConfigReader"/> class
        /// </summary>
        public ConfigReader()
        {
            configAssembly = Assembly.GetCallingAssembly();
            settingSectionGroupName = defaultSectionGroupName;
            settingSectionName = String.Concat(configAssembly.GetName().Name, defaultSectionNameSuffix);
            GetConfig(configAssembly);
        }

        #endregion Constructors

        #region Properties (3)

        /// <summary>
        /// Gets or sets the configuration settings for this instance of the <see cref="ConfigReader"/> class
        /// </summary>
        /// <seealso cref="configSettings"/>
        public Configuration ConfigSettings
        {
            get { return configSettings; }
            set { configSettings = value; }
        }

        /// <summary>
        /// Gets or sets the default section group name for this instance of the <see cref="ConfigReader"/> class
        /// </summary>
        /// <seealso cref="settingSectionGroupName"/>
        public string SectionGroupName
        {
            get { return settingSectionGroupName; }
            set { settingSectionGroupName = value; }
        }

        /// <summary>
        /// Gets or sets the default section name for this instance of the <see cref="ConfigReader"/> class
        /// </summary>
        public string SectionName
        {
            get { return settingSectionName; }
            set { settingSectionName = value; }
        }

        #endregion Properties

        #region Methods (20)

        // Public Methods (20) 

        /// <summary>
        /// Gets the file name of the assembly
        /// </summary>
        /// <returns>File name of the assembly</returns>
        /// <seealso cref="configAssembly"/>
        public string GetAssemblyFilename()
        {
            return GetAssemblyFilename(configAssembly);
        }

        /// <summary>
        /// Gets the file name of the assembly
        /// </summary>
        /// <param name="namedAssembly">The assembly for which to get the file name</param>
        /// <returns>File name of the assembly</returns>
        public string GetAssemblyFilename(Assembly namedAssembly)
        {
            Uri assemblyUri = new Uri(namedAssembly.CodeBase);
            return assemblyUri.LocalPath;
        }

        /// <summary>
        /// Gets the name of the assembly
        /// </summary>
        /// <returns>Name of the assembly</returns>
        /// <seealso cref="configAssembly"/>
        public string GetAssemblyName()
        {
            return GetAssemblyName(configAssembly);
        }

        /// <summary>
        /// Gets the name of the assembly
        /// </summary>
        /// <param name="namedAssembly">The assembly for which to get the name</param>
        /// <returns>Name of the assembly</returns>
        public string GetAssemblyName(Assembly namedAssembly)
        {
            return namedAssembly.GetName().Name;
        }

        /// <summary>
        /// Gets the path to the file for the assembly
        /// </summary>
        /// <returns>Path to the file for the assembly</returns>
        /// <seealso cref="configAssembly"/>
        public string GetAssemblyPath()
        {
            return GetAssemblyPath(configAssembly);
        }

        /// <summary>
        /// Gets the path to the file for the assembly
        /// </summary>
        /// <param name="namedAssembly">The assembly for which to get the path</param>
        /// <returns>Path to the file for the assembly</returns>
        public string GetAssemblyPath(Assembly namedAssembly)
        {
            return Path.GetDirectoryName(namedAssembly.Location);
        }

        /// <summary>
        /// Gets the configuration settings for the assembly
        /// </summary>
        /// <seealso cref="configAssembly"/>
        /// <seealso cref="GetConfig(Assembly)"/>
        public void GetConfig()
        {
            GetConfig(configAssembly);
        }

        /// <summary>
        /// Gets the configuration settings for the assembly
        /// </summary>
        /// <param name="namedAssembly">The assembly for which to get the configuration settings</param>
        /// <seealso cref="configSettings"/>
        public void GetConfig(Assembly namedAssembly)
        {
            ExeConfigurationFileMap fileMap = new ExeConfigurationFileMap();
            fileMap.ExeConfigFilename = GetConfigFileName(namedAssembly);
            configSettings = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None);
        }

        /// <summary>
        /// Gets the file name of the configuration settings file for the assembly
        /// </summary>
        /// <returns>File name of the configuration settings file for the assembly</returns>
        public string GetConfigFileName()
        {
            return GetConfigFileName(configAssembly);
        }

        /// <summary>
        /// Gets the file name of the configuration settings file for the assembly
        /// </summary>
        /// <param name="namedAssembly">The assembly for which to get the configuration settings file name</param>
        /// <returns>File name of the configuration settings file for the assembly</returns>
        /// <seealso cref="configFileExtension"/>
        public string GetConfigFileName(Assembly namedAssembly)
        {
            string configFileName = GetAssemblyFilename(namedAssembly) + configFileExtension;
            if (!File.Exists(configFileName))
            {
                FileNotFoundException configFileException = new FileNotFoundException("Configuration file expected in assembly location.", configFileName);
                throw configFileException;
            }
            return configFileName;
        }

        /// <summary>
        /// Gets the setting value for the specified key
        /// </summary>
        /// <param name="key">Name of the key for the setting</param>
        /// <returns>Value of the setting</returns>
        public string GetSetting(string key)
        {
            return GetSetting(settingSectionGroupName, settingSectionName, key);
        }

        /// <summary>
        /// Gets the setting value for the specified key
        /// </summary>
        /// <param name="sectionGroup">Name of the section group</param>
        /// <param name="sectionName">Name of the section</param>
        /// <param name="key">Name of the key for the setting</param>
        /// <returns>Value of the setting</returns>
        public string GetSetting(string sectionGroup, string sectionName, string key)
        {
            string returnValue;
            returnValue = GetSettingXml(sectionGroup, sectionName, key).InnerText;
            return returnValue;
        }

        /// <summary>
        /// Gets the boolean setting value for the specified key
        /// </summary>
        /// <param name="key">Name of the key</param>
        /// <returns>Value of the setting</returns>
        public bool GetSettingAsBool(string key)
        {
            return GetSettingAsBool(settingSectionGroupName, settingSectionName, key);
        }

        /// <summary>
        /// Gets the boolean setting value for the specified key
        /// </summary>
        /// <param name="sectionGroup">Name of the section group</param>
        /// <param name="sectionName">Name of the section</param>
        /// <param name="key">Name of the key</param>
        /// <returns>Value of the setting</returns>
        public bool GetSettingAsBool(string sectionGroup, string sectionName, string key)
        {
            string val = GetSetting(sectionGroup, sectionName, key);
            bool returnValue = bool.Parse(val);
            return returnValue;
        }

        /// <summary>
        /// Gets the integer setting value for the specified key
        /// </summary>
        /// <param name="key">Name of the key</param>
        /// <returns>Value of the setting</returns>
        public int GetSettingAsInt(string key)
        {
            return GetSettingAsInt(settingSectionGroupName, settingSectionName, key);
        }

        /// <summary>
        /// Gets the integar setting value for the specified key
        /// </summary>
        /// <param name="sectionGroup">Name of the section group</param>
        /// <param name="sectionName">Name of the section</param>
        /// <param name="key">Name of the key</param>
        /// <returns>Value of the setting</returns>
        public int GetSettingAsInt(string sectionGroup, string sectionName, string key)
        {
            string settingValue = GetSetting(sectionGroup, sectionName, key);
            int returnValue = Int32.Parse(settingValue);
            return returnValue;
        }

        /// <summary>
        /// Gets the string collection value for the specified key
        /// </summary>
        /// <param name="key">Name of the key</param>
        /// <returns>Value of the setting</returns>
        public StringCollection GetSettingAsStringCollection(string key)
        {
            return GetSettingAsStringCollection(settingSectionGroupName, settingSectionName, key);
        }

        /// <summary>
        /// Gets the string collection value for the specified key
        /// </summary>
        /// <param name="sectionGroup">Name of the section group</param>
        /// <param name="sectionName">Name of the section</param>
        /// <param name="key">Name of the key</param>
        /// <returns>Value of the setting</returns>
        public StringCollection GetSettingAsStringCollection(string sectionGroup, string sectionName, string key)
        {
            XmlNode valXml = GetSettingXml(sectionGroup, sectionName, key);
            StringCollection returnVal = new StringCollection();
            foreach (XmlNode childNode in valXml.SelectNodes("ArrayOfString/string"))
            {
                returnVal.Add(childNode.InnerText);
            }
            return returnVal;
        }

        /// <summary>
        /// Gets the XML for the specified key
        /// </summary>
        /// <param name="key">Name of the key</param>
        /// <returns>XML for the setting</returns>
        public XmlNode GetSettingXml(string key)
        {
            return GetSettingXml(settingSectionGroupName, settingSectionName, key);
        }

        /// <summary>
        /// Gets the XML for the specified key
        /// </summary>
        /// <param name="sectionGroup">Name of the section group</param>
        /// <param name="sectionName">Name of the section</param>
        /// <param name="key">Name of the key</param>
        /// <returns>Value of the setting</returns>
        public XmlNode GetSettingXml(string sectionGroup, string sectionName, string key)
        {
            XmlNode returnValue;
            ClientSettingsSection appSettings = (ClientSettingsSection)configSettings.GetSection(sectionGroup + "/" + sectionName);
            SettingElement thisSettingElement = appSettings.Settings.Get(key);
            SettingsSerializeAs serSetting = thisSettingElement.SerializeAs;
            SettingValueElement valueElement = thisSettingElement.Value;
            Type valueElementType = valueElement.ElementInformation.Type;
            XmlSerializer xs = new XmlSerializer(typeof(string[]));
            string[] strings = (string[])xs.Deserialize(new XmlTextReader(valueElement.ValueXml.InnerXml, XmlNodeType.Element, null));

            returnValue = thisSettingElement.Value.ValueXml;
            return returnValue;
        }

        #endregion Methods
    }

    /// <summary>
    /// Contains methods that provide information about types and their members.
    /// </summary>
    /// <remarks>See http://handcraftsman.wordpress.com/2008/11/11/how-to-get-c-property-names-without-magic-strings/ for more information.</remarks>
    public static class ReflectionUtility
    {
        #region Methods (1)

        // Public Methods (1) 

        /// <summary>
        /// Gets the name of the specified property.
        /// </summary>
        /// <returns>string value that contains the name of the property</returns>
        public static string GetPropertyName<T>(Expression<Func<T>> expression)
        {
            MemberExpression body = (MemberExpression)expression.Body;
            return body.Member.Name;
        }

        #endregion Methods
    }
}